package com.luobo.shiro.config;

import com.luobo.shiro.service.impl.RedisService;
import com.luobo.shiro.util.JWTUtil;
import io.jsonwebtoken.Claims;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 该版本过滤器实现控制并发登录数动态控制
 * @author : AnWen
 * @version :1.0
 * @email : anwen375@qq.com
 * @since : 2020/3/15 10:49
 */
public class ConcurrentLoginFilter extends SessionControlFilter {

    private Map<String, Deque<Serializable>> map;

    /**
     * 踢出之前登录的/之后登录的用户 默认踢出之前登录的用户
     */
    private boolean kickoutAfter = false;

    /**
     * 同一个帐号最大会话数 默认1
     */
    private int maxSession = 1;

    @Override
    public void setRedisService(RedisService redisService) {
        super.setRedisService(redisService);
        map = (Map) redisService.getHash("shiro:kickout:session:");
        if (map == null) {
            map = new ConcurrentHashMap<>();
        }
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest servletRequest = WebUtils.toHttp(request);
        HttpServletResponse servletResponse = WebUtils.toHttp(response);
        String token = servletRequest.getHeader(MySessionManager.TOKEN_NAME);
        Claims claims = JWTUtil.parseSessionId(token);
        Subject subject = getSubject(request, response);
        Serializable sessionId = subject.getSession().getId();

        if (claims != null && claims.get("username") != null) {

            if (!subject.isAuthenticated() && !subject.isRemembered()) {
                subject.logout();
                servletResponse.setStatus(402);
                super.writeMessage(servletResponse, "下线通知：当前账号另一地点登录， 您被迫下线。若非本人操作，您的登录密码很可能已经泄露，请及时改密。");
                return false;
            }
            String username = claims.get("username", String.class); //token中的username
            Deque<Serializable> deque = map.computeIfAbsent(username, k -> new LinkedList<Serializable>());

            if (!deque.contains(sessionId)) {
                deque.push(sessionId);
            }

            while (deque.size() > maxSession) {
                Serializable remove = null;
                if (kickoutAfter) { //如果踢出后者
                    remove = deque.removeFirst();
                } else { //否则踢出前者
                    remove = deque.removeLast();
                }
                getRedisService().del("shiro:session:" + remove);
            }
            map.put(username, deque);
            getRedisService().setHash("shiro:kickout:session:", map);
        } else {
            subject.logout();
            servletResponse.setStatus(401);
            super.writeMessage(servletResponse, "token无效，请重新登录");
            return false;
        }
        return true;
    }

    public boolean isKickoutAfter() {
        return kickoutAfter;
    }

    public void setKickoutAfter(boolean kickoutAfter) {
        this.kickoutAfter = kickoutAfter;
    }

    public void setMaxSession(int maxSession) {
        this.maxSession = maxSession;
    }

}
